package com.cory.module.oplog.core.task;

import java.lang.reflect.Field;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
import com.beust.jcommander.internal.Lists;
import com.cory.boot.domain.bean.Principal;
import com.cory.boot.domain.enumdef.EOperateType;
import com.cory.module.oplog.core.handler.BaseExtendedTypeHandler;
import com.cory.module.oplog.core.handler.BuiltinTypeHandler;
import com.cory.module.oplog.core.model.AttributeModel;
import com.cory.module.oplog.core.wrapper.ClazzWrapper;
import com.cory.module.oplog.core.wrapper.FieldWrapper;
import com.cory.module.oplog.domain.entity.Oplog;
import com.cory.module.oplog.domain.entity.OplogAttribute;
import com.cory.module.oplog.service.OplogService;

@Component
@Slf4j
public class LogObjectTask {
    @Autowired(required=false)
    private BaseExtendedTypeHandler baseExtendedTypeHandler;
    
    @Autowired
    private OplogService oplogService;
    
    @Autowired
    private TaskExecutor executor;
    
    public void addLog(Principal principal, Class<?> clazz, Object objectId, Integer operateType, String operateName, String remarks) {
        executor.execute(() -> {
            try {
                ClazzWrapper clazzWrapper = new ClazzWrapper(clazz, null); // issue #1
                if (!clazzWrapper.isWithLogTag()) {
                    log.warn("? , OpLog annotation is not present", clazz);
                    return;
                }
                
                Oplog log = new Oplog(clazzWrapper.getObjectAlias(), clazzWrapper.getObjectName(), objectId.toString(), clazzWrapper.getObjectData(),
                        principal.getUserId(), principal.getUserName(), operateType, operateName, remarks, principal.getRequestIp());
                oplogService.save(log);
            } catch(Exception e) {
                e.printStackTrace();
            }
        });
    }
    
    public void log4Type(Principal principal, Object object, EOperateType opLogType) {
        executor.execute(() -> {
            try {
                Class<?> modelClazz = object.getClass();
                ClazzWrapper clazzWrapper = new ClazzWrapper(modelClazz, object); // issue #1
                if (!clazzWrapper.isWithLogTag()) {
                    log.warn("? , OpLog annotation is not present", modelClazz);
                    return;
                }
                
                Oplog log = new Oplog(clazzWrapper.getObjectAlias(), clazzWrapper.getObjectName(), clazzWrapper.getObjectId(), clazzWrapper.getObjectData(),
                        principal.getUserId(), principal.getUserName(), opLogType.key, opLogType.name, null, principal.getRequestIp());
                oplogService.save(log);
            } catch(Exception e) {
                e.printStackTrace();
            }
        });
    }
    
    public void log4Update(Principal principal, Object oldObject, Object newObject) {
        executor.execute(() -> {
            try {
                this.execute4Update(principal, oldObject, newObject);
            } catch(Exception e) {
                e.printStackTrace();
            }
        });
    }
    
    private void execute4Update(Principal principal, Object oldObject, Object newObject) throws Exception {
        Class<?> modelClazz = newObject.getClass();
        Class<?> oldModelClazz = oldObject.getClass();
        
        if (!oldModelClazz.equals(modelClazz)) {
            log.warn("? and ? not same", oldModelClazz, modelClazz);
            return;
        }
        
        ClazzWrapper clazzWrapper = new ClazzWrapper(modelClazz, oldObject); // issue #1
        if (!clazzWrapper.isWithLogTag()) {
            log.warn("? , OpLog annotation is not present", modelClazz);
            return;
        }
        
        
        List<Field> fieldList = clazzWrapper.getFieldList();
        
        List<OplogAttribute> attributes = Lists.newArrayList();
        
        for (Field field : fieldList) {
            field.setAccessible(true);
            FieldWrapper fieldWrapper = new FieldWrapper(field, field.get(oldObject), field.get(newObject));
            
            if (!fieldWrapper.isWithLogTag() || fieldWrapper.getNewValue() == null) {
                continue;
            }
            
            if (!nullableEquals(fieldWrapper.getOldValue(), fieldWrapper.getNewValue())) {
                AttributeModel baseAttributeModel;
                if (fieldWrapper.isWithExtendedType()) {
                    baseAttributeModel = handleExtendedTypeItem(fieldWrapper);
                } else {
                    baseAttributeModel = handleBuiltinTypeItem(fieldWrapper);
                }

                if (baseAttributeModel != null) {
                    OplogAttribute attribute = new OplogAttribute();
                    BeanUtils.copyProperties(baseAttributeModel, attribute);
                    attributes.add(attribute);
                }
            }
        }
        
        if (attributes.size() > 0) {
            Oplog log = new Oplog(clazzWrapper.getObjectAlias(), clazzWrapper.getObjectName(), clazzWrapper.getObjectId(), clazzWrapper.getObjectData(),
                    principal.getUserId(), principal.getUserName(), EOperateType.UPDATE.key, EOperateType.UPDATE.name, null, principal.getRequestIp());
            oplogService.saveLog(log, attributes);
        }
    }

    private AttributeModel handleBuiltinTypeItem(FieldWrapper fieldWrapper) {
        BuiltinTypeHandler builtinType = BuiltinTypeHandler.NORMAL;
        if (fieldWrapper.getLogTag() != null) {
            builtinType = fieldWrapper.getLogTag().builtinType();
        }

        AttributeModel handlerOutput = builtinType.handlerAttributeChange(fieldWrapper);

        if (handlerOutput != null) {
            // 固定值
            handlerOutput.setAttributeName(fieldWrapper.getAttributeName());
            handlerOutput.setAttributeAlias(fieldWrapper.getAttributeAlias());
            handlerOutput.setAttributeType(builtinType.name());
            return handlerOutput;
        } else {
            return null;
        }
    }

    private AttributeModel handleExtendedTypeItem(FieldWrapper fieldWrapper) {
        AttributeModel baseAttributeModel = baseExtendedTypeHandler.handleAttributeChange(
                fieldWrapper.getExtendedType(),
                fieldWrapper.getAttributeName(),
                fieldWrapper.getAttributeAlias(),
                fieldWrapper.getOldValue(),
                fieldWrapper.getNewValue()
        );

        if (baseAttributeModel != null) {
            if (baseAttributeModel.getAttributeType() == null) {
                baseAttributeModel.setAttributeType(fieldWrapper.getExtendedType());
            }
            if (baseAttributeModel.getAttributeName() == null) {
                baseAttributeModel.setAttributeName(fieldWrapper.getAttributeName());
            }
            if (baseAttributeModel.getAttributeAlias() == null) {
                baseAttributeModel.setAttributeAlias(fieldWrapper.getAttributeAlias());
            }
        }

        return baseAttributeModel;
    }

    // issue #2
    private boolean nullableEquals(Object a, Object b) {
        return (a == null && b == null) || (a != null && a.equals(b));
    }
}
